import pickle
import Tkinter

from threading import Thread
from os import sep as OS_SEP

import msg
import trapbox

from shore import *


def arealize(lengths, area_proc_fn=1.0):
    """
    """
    try:
        area = area_proc_fn(lengths)
    except TypeError:
        area = area_proc_fn
        for l in lengths:
            area *= l
    return area


class Chowder:
    """
    """
    def __init__(self, caliper, prev_measurements=None,
                 leaf_start_n=1, Ai=0, cont_flag=False):
        self.gui = False
        self.caliper = caliper
        self.caliper.holding_measurements = []
        #self.cont = 0  # Continue from last or start a new run?
        self.config = {'basename': '', 'area_factor': 1.0}
        self.A = Ai
        self.leaf_n = leaf_start_n - 1  # Let grab_measurements() count
        self.measurements = {'timestamp': [], w: [], l: [], a: [], leaf_n: []}
        self._integrate_previous_measurements(prev_measurements, cont_flag)
        self.sensors = {w: None, l: None, a: None, A: None, leaf_n: None}
        # Finally set the options for the caliper data-listener thread
        # and start it.
        self.live_opts = {'target': self.caliper.monitor,
                          'args': (self.leaf_area_wrapper,)}
        self.start_caliper()

    def _integrate_previous_measurements(self, prev_measurements=None,
                                         cont_flag=False):
        self.current_run = len(prev_measurements or ()) + 1
        self.complete_measurements = prev_measurements or {}
        self.complete_measurements[self.current_run] = self.measurements

    '''
    def reset(self):
        # TODO:2009-06-05:damosurfer: Actually implement a useful
        # version of a reset function.
        try:
            # self.sensors is meant to be set externally as TkInter
            # StringVar(s) when opening a GUI
            for i in self.sensors:
                self.sensors[i].set('')
        except:
            # But if it gets called from the CLI handle them gracefully
            self.sensors = {w: None, l: None, a: None, A: None}

    def __reset__(self, start_run=1, prev_measurements=None):
        self.caliper.holding_measurements = []
        try:
            for i in self.sensors:
                self.sensors[i].set('')
        except:
            self.sensors = {w: None, l: None, a: None, A: None}
        self.cont = 0 # Continue from last or start a new run?
        self.current_run = start_run
        self.leaf_n = 0
        self.config = {'basename': '', 'area_factor': 1.0}
        self.measurements = {'timestamp': [], w: [], l: [], a: [], A: [0]}
        self.complete_measurements = {self.current_run: self.measurements}
        self.start_caliper()
    '''

    def stop_caliper(self):
        self.caliper.stop_flag = 1
        sleep(1)  # Wait for caliper to wake from its while-loop sleep

    def ready_caliper(self):
        self.caliper.stop_flag = 0

    def start_caliper(self):
        self.stop_caliper()
        self.ready_caliper()
        self.live_caliper = Thread(**self.live_opts)
        self.live_caliper.start()

    def restart_caliper(self):
        self.start_caliper()

    #def load_previous_measurements(self, saved_measurements):
    #    """
    #    """
    #    run = len(saved_measurements)
    #    for m in ('timestamp', w, l, a):
    #       self.measurements[m] = saved_measurements[run][m][:]
    #    self.config = saved_measurements['config']
    #    self.current_run = run + (1 - self.cont)
    #    self.leaf_n = len(self.measurements[a])
    #   self.measurements[A][0] = sum(self.measurements[a])

    def write_temp_file(self):
        elements = ['%d' % self.leaf_n] + ['%.5f' % self.measurements[m][-1]
                                           for m in ('timestamp', w, l, a)]
        line = '%s\n' % ', '.join(elements)
        trapbox.tempfile.write(line)
        trapbox.tempfile.flush()

    def grab_measurements(self, holding_measurements):
        ts = timestamp()
        af = self.config['area_factor']
        measurements = self.measurements
        # Pop the length first, as it is last on the list, then pop the
        # width.  The readings are in mm so divide by 10 to scale them
        # to cm.  cX: current measurement type X.
        cl = holding_measurements.pop() / 10
        cw = holding_measurements.pop() / 10
        ca = self.leaf_expansion_check(self.leaf_n, arealize([cw, cl], af))
        if ca is not None:
            measurements[leaf_n].append(self.leaf_n)
            measurements['timestamp'].append(ts)
            for m, v in zip(wla, (cw, cl, ca)):
                measurements[m].append(v)
            self.A = self.calculate_total_area()
            return 0
        self.leaf_n -= (self.current_run - 1) and -1 or 1
        return 1

    def leaf_area_wrapper(self, holding_measurements):
        """
        """
        hm_n = len(holding_measurements)
        if hm_n == 1:
            self.leaf_n += (self.current_run - 1) and -1 or 1
            self.update_gui('partial_update', holding_measurements[0])
            notify(hm_n)
        elif hm_n == 2:
            gm_error_status = self.grab_measurements(holding_measurements)
            if not gm_error_status:
                notify(hm_n)
                self.write_temp_file()
                self.update_gui('full_update')
            else:
                notify(1, 'warn')
                self.update_gui('warn')

    def leaf_expansion_check(self, leaf, area):
        pr = self.current_run - 1  # previous run
        if pr and leaf in self.complete_measurements[pr][leaf_n]:
            yesterleaf = self.complete_measurements[pr][leaf_n].index(leaf)
            yesterleaf_size = self.complete_measurements[pr][a][yesterleaf]
            if area <= yesterleaf_size:
                area = None
        return area

    def calculate_total_area(self):
        cr = self.current_run
        pr = cr - 1
        A_ = self.A + self.measurements[a][-1]
        if pr:
            yester__a_s = self.complete_measurements[pr][a]
            yester_leaves = self.complete_measurements[pr][leaf_n]
            if self.leaf_n >= yester_leaves[0]:
                if self.leaf_n in yester_leaves:
                    A_ -= yester__a_s[yester_leaves.index(self.leaf_n)]
        return A_

    def update_gui(self, update_type, live_measurement=0):
        """
        """
        if not self.gui:
            if update_type == 'full_update':
                return self.update_cli()
            else:
                return None
        if update_type == 'partial_update':
            self.sensors[leaf_n].set(self.leaf_n)
            self.sensors[w].set('%.3f' % (live_measurement / 10))
            self.sensors[l].set('')
            self.sensors[a].set('')
        elif update_type == 'full_update':
            for m in self.sensors:
                if m not in ('leaf_n', 'A'):
                    self.sensors[m].set('%.3f' % self.measurements[m][-1])
            self.sensors[A].set('%.3f' % self.A)
        elif update_type == 'initial':
            self.sensors[A].set('%.3f' % self.A)
        elif update_type == 'warn':
            for m in wla:
                self.sensors[m].set('STOP')


    def update_cli(self):
        """
        """
        print msg.CLI_UPDATE_STRING % \
            tuple([self.measurements[m][-1] for m in (w, l, a)]) + (self.A,)

